/**
 * SlotDuino
 * 
 * Copyright (c) 2011/2012 Thiago Hummel Moreira, Ricardo Mizuuti, Rafael Mufato, Bruno Sakai, Andre Turina
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Microsoft.SPOT;

namespace SlotDaemon.Sockets
{
    class TcpServer
    {
        const byte _maxWorkers = 2;

        private int _port;
        private IPAddress _address;
        private bool _stopThreads;
        private ArrayList _workerThreads;
        private Socket _listenSocket;
        private Thread _thdListener;
        private Thread _thdCleaner;

        public event ClientConnectedEventHandler ClientConnected;

        #region Constructors
        public TcpServer()
            : this(80)
        {
        }

        public TcpServer(int port)
            : this(port, IPAddress.Any)
        {
            
        }

        public TcpServer(int port, IPAddress address)
        {
            _address = address;
            _port = port;

            _stopThreads = true;
            _workerThreads = new ArrayList();
        }
        #endregion

        #region Public Properties
        public int Port
        {
            get { return _port; }
        }

        public IPAddress Address
        {
            get { return _address; }
        }
        #endregion

        public bool Start()
        {
            try
            {
                if (_stopThreads)
                {
                    _stopThreads = false;

                    _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    _listenSocket.Bind(new IPEndPoint(_address, _port));
                    _listenSocket.Listen(_maxWorkers);

                    //Start the listener
                    _thdListener = new Thread(new ThreadStart(ListenerThread));
                    _thdListener.Start();

                    //Start the cleaner
                    _thdCleaner = new Thread(new ThreadStart(RemoveWorkerThreads));
                    _thdCleaner.Start();
                }
            }
            catch (Exception)
            {
                Stop();
                return false;
            }

            return true;
        }
        public bool Stop()
        {
            _stopThreads = true;

            return false;
        }

        private void ListenerThread()
        {
            //Ensure the socket will get closed when the thread stops/dies
            using (_listenSocket)
            {
                while (!_stopThreads)
                {
                    Socket client = null;

                    try
                    {
                        client = _listenSocket.Accept();
                    }
                    catch (Exception)
                    {
                        break;
                    }

                    if (client == null)
                        continue;

                    CreateWorkerThread(client);
                }
            }

#if DEBUG
            Debug.Print("Listener dies...");
#endif
        }

        private bool OnClientConnected(IPAddress address)
        {
            ClientConnectedEventHandler handler = ClientConnected;

            bool res = true;

            if (handler != null)
                res = handler(this, new ClientConnectedEventArgs(address));

            return res;
        }

        private void CreateWorkerThread(Socket client)
        {
            int workerCount;

            while (!_stopThreads)        // TODO: add timeout
            {
                lock (_workerThreads)
                {
                    workerCount = _workerThreads.Count;
                }

                if (workerCount < _maxWorkers)
                    break;

                Thread.Sleep(10);
            }

            ResquestProcessor processor = new ResquestProcessor(ref client);
            Thread thd = new Thread(processor.ProcessRequest);
            thd.Start();

            lock (_workerThreads)
            {
                _workerThreads.Add(thd);
            }
        }

        private void RemoveWorkerThreads()
        {
            while (!_stopThreads)
            {
                lock (_workerThreads)
                {
                    if (_workerThreads.Count > 0)
                    {
                        for (int i = _workerThreads.Count - 1; i >= 0; i--)
                        {
                            if (((Thread)_workerThreads[i]).ThreadState == ThreadState.Stopped)
                            {
                                _workerThreads.RemoveAt(i);
                            }
                        }
                    }
                }

                Thread.Sleep(300);
            }
#if DEBUG
            Debug.Print("Cleaner dies...");
#endif
        }
    }
}
